package org.vaadinpa.data;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import org.vaadinpa.Conf;
import org.vaadinpa.exceptions.NotEnoughFundsException;

public class Planet implements Cloneable {

	public String name = "unnamed";
	public String ruler = "unnamed";
	public final Coordinate coords;
	public TechLevel level = TechLevel.LOWEST;

	public int score;
	private int actionScore;

	protected Map<Resource, Integer> resources = new HashMap<Resource, Integer>();
	public Map<Resource, Integer> roids = new HashMap<Resource, Integer>();

	public List<Fleet> fleets = new ArrayList<Fleet>();
	public Fleet homeFleet;

	public int availableRoidScans;
	public int availablePlanetScans;

	public List<NewsItem> news = new LinkedList<NewsItem>();

	public Map<Coordinate, PlanetaryScan> scans = new HashMap<Coordinate, PlanetaryScan>();

	public Planet(Coordinate coordinates) {

		coords = coordinates;

		Fleet f = new Fleet(coordinates, "Fleet 1");
		fleets.add(f);
		f = new Fleet(coordinates, "Fleet 2");
		fleets.add(f);
		f = new Fleet(coordinates, "Home Fleet");
		homeFleet = f;

		for (Resource r : Resource.valuesWithoutNew()) {
			resources.put(r, 0);
		}
		for (Resource r : Resource.values()) {
			roids.put(r, 0);
		}
	}

	public void addBasicResources() {
		resources.put(Resource.METAL, Conf.STARTING_CAPITAL_OF_EACH);
		resources.put(Resource.CRYSTAL, Conf.STARTING_CAPITAL_OF_EACH);
		resources.put(Resource.GOLD, Conf.STARTING_CAPITAL_OF_EACH);
		resources.put(Resource.NEW, 0);

		roids.put(Resource.METAL, Conf.STARTING_ROIDS[0]);
		roids.put(Resource.CRYSTAL, Conf.STARTING_ROIDS[1]);
		roids.put(Resource.GOLD, Conf.STARTING_ROIDS[2]);
		roids.put(Resource.NEW, Conf.STARTING_ROIDS[3]);
	}

	public void updateTotalScore() {
		int wealth = 0;

		wealth += getTotalInitedRoids() * Conf.POINTS_PER_NORM_ROID;
		wealth += roids.get(Resource.NEW) * Conf.POINTS_PER_NEW_ROID;

		wealth += getTotalResources() * Conf.POINTS_PER_RESOURCE;

		for (Fleet f : fleets) {
			for (Ship s : Ship.getAvailableShips()) {
				wealth += s.getTotalPrice() * f.get(s) * Conf.POINTS_PER_FLEET;
			}
		}

		score = actionScore + wealth;
	}

	public int getTotalInitedRoids() {
		int total = 0;
		for (Resource r : Resource.valuesWithoutNew()) {
			total += roids.get(r);
		}
		return total;
	}

	public int getTotalRoids() {
		int total = 0;
		for (Resource r : Resource.values()) {
			total += roids.get(r);
		}
		return total;
	}

	public int getTotalResources() {
		int total = 0;
		for (Resource r : Resource.valuesWithoutNew()) {
			total += resources.get(r);
		}
		return total;
	}

	public void updateResources() {

		for (Resource r : Resource.valuesWithoutNew()) {
			resources.put(r, resources.get(r) + roids.get(r) * Conf.ROID_RESOURCE_RATE_NORM_LEVEL[level.ordinal()]);
			resources.put(r,
					resources.get(r) + roids.get(Resource.NEW) * Conf.ROID_RESOURCE_RATE_NEW_LEVEL[level.ordinal()]);
		}
	}

	public void levelUp() {
		if (level.ordinal() < TechLevel.values().length - 1) {
			level = TechLevel.values()[level.ordinal() + 1];
		}
	}

	public synchronized Map<Resource, Integer> giveUpRoids(int attackingDamage) {

		// get theoretical total number of roids
		Map<Resource, Integer> roidsToGive = new HashMap<Resource, Integer>();
		int numRoidsToGive = attackingDamage / Conf.COMBAT_ROID_ARMOR;

		// divide total according to roid type spread
		float totalRoids = getTotalRoids();
		for (Resource r : roids.keySet()) {
			int roidsOfThis = Math.round(numRoidsToGive * roids.get(r) / totalRoids);
			roidsToGive.put(r, roidsOfThis);
		}

		// reduce number of given roids so that it is less than 25% of total
		Resource[] ra = Resource.values();
		// start from a random resource, and iterate through
		int indexToRemove = new Random().nextInt(ra.length);
		while (numRoidsToGive / totalRoids > Conf.MAX_ROIDS_LOST_PERCENTAGE) {

			Resource r = ra[indexToRemove++ % ra.length];
			if (roidsToGive.get(r) > 0) {
				roidsToGive.put(r, roidsToGive.get(r) - 1);
				numRoidsToGive--;
			} else {
				// let loop run to next resource and take from that
			}
		}

		// remove from planet
		for (Entry<Resource, Integer> e : roidsToGive.entrySet()) {
			roids.put(e.getKey(), roids.get(e.getKey()) - e.getValue());
		}

		return roidsToGive;
	}

	public synchronized void withdrawFunds(Resource r, int amount) throws NotEnoughFundsException {
		if (resources.get(r) < amount) {
			throw new NotEnoughFundsException();
		}

		resources.put(r, resources.get(r) - amount);
	}

	public synchronized void withdrawFunds(int metal, int crystal, int gold) throws NotEnoughFundsException {
		if (resources.get(Resource.METAL) < metal) {
			throw new NotEnoughFundsException();
		} else if (resources.get(Resource.CRYSTAL) < crystal) {
			throw new NotEnoughFundsException();
		} else if (resources.get(Resource.GOLD) < gold) {
			throw new NotEnoughFundsException();
		}

		resources.put(Resource.METAL, resources.get(Resource.METAL) - metal);
		resources.put(Resource.CRYSTAL, resources.get(Resource.CRYSTAL) - crystal);
		resources.put(Resource.GOLD, resources.get(Resource.GOLD) - gold);
	}

	public synchronized void depositFunds(int metal, int crystal, int gold) {
		resources.put(Resource.METAL, resources.get(Resource.METAL) + metal);
		resources.put(Resource.CRYSTAL, resources.get(Resource.CRYSTAL) + crystal);
		resources.put(Resource.GOLD, resources.get(Resource.GOLD) + gold);
	}

	public int getFunds(Resource r) {
		return resources.get(r);
	}

	public void addRoids(Resource r, int asteroids) {
		Integer existing = roids.get(r);
		if (existing == null) {
			existing = 0;
		}
		roids.put(r, existing + asteroids);
	}

	public void addNews(NewsItem i) {
		news.add(0, i);
	}

	public boolean canLevelUp() {
		int m = Conf.COST_LEVEL_UP[level.ordinal()][0];
		int c = Conf.COST_LEVEL_UP[level.ordinal()][1];
		int g = Conf.COST_LEVEL_UP[level.ordinal()][2];

		return m <= resources.get(Resource.METAL) && c <= resources.get(Resource.CRYSTAL)
				&& g <= resources.get(Resource.GOLD);
	}

	@Override
	public String toString() {
		return name + " (" + coords.toString() + ")";
	}

	@Override
	public boolean equals(Object o) {
		if (this == o) {
			return true;
		}
		if (o == null || !this.getClass().equals(o.getClass())) {
			return false;
		}

		Planet other = (Planet) o;
		return coords.equals(other.coords);
	}

	@Override
	public int hashCode() {
		return coords.hashCode();
	}

	public synchronized void convertRoid(Resource r) throws NotEnoughFundsException {

		int cost = Conf.ROID_INIT_COST_LEVEL[level.ordinal()];
		if (resources.get(r) < cost) {
			throw new NotEnoughFundsException();
		}
		resources.put(r, resources.get(r) - cost);

		roids.put(r, roids.get(r) + 1);
		roids.put(Resource.NEW, roids.get(Resource.NEW) - 1);
	}

	@Override
	public Planet clone() {
		try {

			Planet clone = (Planet) super.clone();
			clone.news = new ArrayList<NewsItem>();
			clone.scans = new HashMap<Coordinate, PlanetaryScan>();

			clone.homeFleet = homeFleet.clone();
			clone.fleets = new ArrayList<Fleet>();
			for (Fleet f : fleets) {
				clone.fleets.add(f.clone());
			}

			clone.roids = new HashMap<Resource, Integer>();
			for (Entry<Resource, Integer> e : roids.entrySet()) {
				clone.roids.put(e.getKey(), e.getValue());
			}
			clone.resources = new HashMap<Resource, Integer>();
			for (Entry<Resource, Integer> e : resources.entrySet()) {
				clone.resources.put(e.getKey(), e.getValue());
			}

			return clone;
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}

		return null;
	}

	public int getTotalShips(Ship s) {
		int i = 0;
		for (Fleet f : fleets) {
			i += f.get(s);
		}
		i += homeFleet.get(s);
		return i;
	}

	public int getTotalShips() {
		int i = 0;
		for (Fleet f : fleets) {
			for (Ship s : Ship.getAvailableShips()) {
				i += f.get(s);
			}
		}
		for (Ship s : Ship.getAvailableShips()) {
			i += homeFleet.get(s);
		}
		return i;
	}

	public boolean owns(Fleet f) {
		return homeFleet == f || fleets.contains(f);
	}

	public void serialize(StringBuilder sb, Collection<BattleNewsItem> battles) {

		sb.append("<planet x=\"" + coords.x + "\" y=\"" + coords.y + "\" >\n");

		sb.append("<name>" + name + "</name>\n");
		sb.append("<ruler>" + ruler + "</ruler>\n");
		sb.append("<score>" + score + "</score>\n");
		sb.append("<level>" + level.ordinal() + "</level>\n");

		sb.append("<availableRoidScans>" + availableRoidScans + "</availableRoidScans>\n");
		sb.append("<availablePlanetScans>" + availablePlanetScans + "</availablePlanetScans>\n");

		sb.append("<actionScore>" + actionScore + "</actionScore>\n");
		sb.append("<score>" + score + "</score>\n");

		sb.append("<homefleet>\n");
		homeFleet.serialize(sb);
		sb.append("</homefleet>\n");
		sb.append("<fleets>\n");
		for (Fleet f : fleets) {
			f.serialize(sb);
		}
		sb.append("</fleets>\n");

		sb.append("<scans>\n");
		for (PlanetaryScan s : scans.values()) {
			s.serialize(sb);
		}
		sb.append("</scans>\n");

		sb.append("<news>\n");
		for (NewsItem n : news) {
			if (n instanceof BattleNewsItem) {
				battles.add((BattleNewsItem) n);
			} else {
				n.serialize(sb);
			}
		}
		sb.append("</news>\n");

		sb.append("<roids>\n");
		for (Entry<Resource, Integer> e : roids.entrySet()) {
			sb.append("<roid type=\"" + e.getKey() + "\" num=\"" + e.getValue() + "\"/>\n");
		}
		sb.append("</roids>\n");

		sb.append("<resources>\n");
		for (Entry<Resource, Integer> e : roids.entrySet()) {
			sb.append("<res type=\"" + e.getKey() + "\" num=\"" + e.getValue() + "\"/>\n");
		}
		sb.append("</resources>\n");

		sb.append("</planet>\n");
	}
}
